#include "RedundancyGenerator.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctime>

#define toSeconds(t) (t.tv_sec + (t.tv_usec/1000000.))

namespace miniprojeto {
namespace Redundancy {

RedundancyGenerator::RedundancyGenerator() {
	originalFileName = "";
	finalFileName = "";
	if (sem_init(&semWaitWorkers, 0, 0) < 0) {
		cerr<<"Erro iniciando o semafaro Works"<<endl;
	}
	if (sem_init(&mutex, 0, 1) < 0) {
		cerr<<"Erro iniciando o Mutex"<<endl;
	}
	blocksList = new Lista_Cresc<CRCBlocks*>();
}

RedundancyGenerator::~RedundancyGenerator() {
	blocksList->ApagaLista();
	delete blocksList;
	sem_destroy	(&semWaitWorkers);
	sem_destroy (&mutex);
}

void RedundancyGenerator::setOriginalFileName(string fname) {
	if (fname != "") {
		this->originalFileName = fname;	
	} else {
		BadArgumentException except ((char*)"RedundancyGenerator::setOriginalFileName()");
		throw except;
	}
}

void RedundancyGenerator::setFinalFileName(string fname) {
	if (fname != "") {
		this->finalFileName = fname;	
	} else {
		BadArgumentException except ((char*)"RedundancyGenerator::setFinalFileName()");
		throw except;
	}
}

void RedundancyGenerator::writeFileHeader() {
	out.write((char*) "REDUFILE", 8);					/*8 bytes*/
	int found = originalFileName.find_last_of('/');
	string fileName = originalFileName.substr(found +1);
	short nameLen = (short) fileName.size();	
	out.write((char*) &nameLen, sizeof(short));			/*2 bytes*/
	out.write((char*) fileName.c_str(), nameLen);		/*variável*/
	out.write((char*) &blocksNumber, sizeof(int));		/*4 bytes*/
	out.write((char*) &fileLen, sizeof(long));			/*8 bytes*/
	time_t lt= time(NULL);
	string stime = asctime(localtime(&lt));
	out.write((char*) stime.c_str(),  stime.size()); 	/* 25 bytes*/
	out.flush();
}

void RedundancyGenerator::openFiles() {
	in.open(originalFileName.c_str(), ios::binary );
	
	struct stat s;
	if (stat(originalFileName.c_str(), &s)) {
		cerr<<"Erro obtendo informações do arquivo original";	
	}
	this->fileLen = s.st_size;
	this->blocksNumber = (int) (fileLen/BLOCK_SIZE) + 1;
	
	out.open(finalFileName.c_str(), ios::binary );
}

void RedundancyGenerator::init() {
	if (this->originalFileName == "" || this->finalFileName == "") {
		string temp = "RedudancyGenerator::init() ->  ";
		temp += "originalFileName == " + originalFileName + "\n";
		temp += "finalFileName == " + finalFileName + "\n";
		InitializationException except(temp);
		throw except;
	}
	
	openFiles();
	
	cout<<"0%                                           100%\n";
	
	writeFileHeader();
	
	bool end = false;
	long byteCount = 0;
	unsigned char* buffer;

	while (!end) {
		CRCBlocks* block;
		CRCWorker* worker;
		int nWorkers = (fileLen - byteCount) / BLOCK_SIZE + 1;
		if (nWorkers > 4) {
			nWorkers = 4;	
		}
		this->setWorkersCreated(nWorkers);
		for (int i = 0; i < 4; i++) {
			buffer = new unsigned char [BLOCK_SIZE + 2]; /*aloca buffer*/
			assert(buffer);
			if ((fileLen - byteCount) > BLOCK_SIZE) {
				byteCount += BLOCK_SIZE;
				in.read((char*) buffer, BLOCK_SIZE);;
				block = new CRCBlocks(buffer, BLOCK_SIZE+2); /*aloca bloco*/
				worker = new CRCWorker(); /*aloca worker*/
				worker->setCRCBlock(block);
				worker->addFinishListener(this);
				worker->setNumber(i+1);
				worker->start();
			} else {
				int blockSpecialSize = fileLen - byteCount;
				byteCount += blockSpecialSize;
				if (blockSpecialSize != 0) {
					in.read((char*) buffer, blockSpecialSize);
					block = new CRCBlocks(buffer, blockSpecialSize); /*aloca bloco*/
					worker = new CRCWorker(); /*aloca worker*/
					worker->setCRCBlock(block);
					worker->addFinishListener(this);
					worker->setNumber(i+1);
					worker->start();
				}
				break;
			}
			printStatus(byteCount);
		}
		
		waitWorkersFinished();
		
		evalueteXor();
		
		writeBlocks();
		
		if (fileLen == byteCount) {
			end = true;	
		}		
	}
	cout<<endl;
	
	out.close();
	in.close();
}

void RedundancyGenerator::waitWorkersFinished() {
	sem_wait(&semWaitWorkers);
	/*este método apenas espera todas as threads terminarem*/
}
		
void RedundancyGenerator::evalueteXor() {
	CRCBlocks* block;
	XORByteArray* xorA;
	if (!blocksList->Vazia()) {
		xorA = new XORByteArray(BLOCK_SIZE + 2);
		blocksList->Primeiro(block);
		xorA->addByteArray(block->getBytes());
		while (blocksList->Proximo(block)) {
			xorA->addByteArray(block->getBytes());
		}
		unsigned char* blockXor = xorA->evaluateXor();
		CRCBlocks* newBlock = new CRCBlocks(blockXor);
		newBlock->setNumber(10);
		blocksList->Insere(newBlock);
		delete xorA;
	}
}
		
void RedundancyGenerator::writeBlocks() {
	CRCBlocks* block;
	if (!blocksList->Vazia()) {
		blocksList->Primeiro(block);
		if (!(block->writeFile(out))) {
			cerr<<"Erro escrevendo o arquivo de saida"<<endl;
			exit(1);	
		}
		delete block;
		while (blocksList->Proximo(block)) {
			if (!(block->writeFile(out))) {
				cerr<<"Erro escrevendo o arquivo de saida"<<endl;
				exit(1);	
			}
			delete block;
		}
		blocksList->ApagaLista();
	}
}

/* syncronized */
void RedundancyGenerator::recevFinishWorkNotification(void* worker) {
	sem_wait(&mutex);
	CRCWorker* crcWorker = (CRCWorker*) worker; /*cast*/
	CRCBlocks* block = crcWorker->getCRCBlocks();
	blocksList->Insere(block);
	workersCreated--;
	delete crcWorker;
	if (workersCreated == 0) {
		sem_post(&semWaitWorkers);
	}
	sem_post(&mutex);
}

/* syncronized */
void RedundancyGenerator::setWorkersCreated(int nWorkers) {
	sem_wait(&mutex);
	this->workersCreated = nWorkers;
	sem_post(&mutex);		
}

void RedundancyGenerator::printStatus (long byteCount) {
	static int status = 0;
	int nStatus = (int) ((((float)byteCount)/fileLen)*100);
	while (nStatus >= status+2) {
		cout<<"=";
		cout.flush();
		status+=2;
	}
}

}
}